﻿using System;
using System.Collections.Generic;
using System.Linq;
using JellystonePark.Model;

namespace JellystonePark.Model
{
    public class JellystoneModel : IDisposable
    {
        public enum GameDifficulty { Easy, Moderate, Hard }

        #region Fields

        private int _basketsAll;
        private int _basketsFounded;
        private int _steps;
        private int _currentLevel;
        private int _gameTime;
        private bool _isLevelOver;
        private bool _isLevelWon;
        private JellystonePlayer _player;
        private List<JellystoneTable> _initialTables;
        private List<JellystoneTable> _tables;
        private PCLTimer _gameTimer;
        private PCLTimer _keeperTimer;
        private bool _disposed;

        #endregion

        #region Properties

        /// <summary>
        /// Játéktábla lekérdezése.
        /// </summary>
        public List<JellystoneTable> Tables { get { return _tables; } }

        /// <summary>
        /// Megtett lépések számának lekérdezése.
        /// </summary>
        public int Steps { get { return _steps; } }

        /// <summary>
        /// A begyűjtendő kosarak száma.
        /// </summary>
        public int BasketsAll { get { return _basketsAll; } }

        /// <summary>
        /// A már begyűjtött kosarak száma.
        /// </summary>
        public int BasketsFounded { get { return _basketsFounded; } }

        /// <summary>
        /// Aktuális pálya.
        /// </summary>
        public int CurrentLevel { get { return _currentLevel; } }

        /// <summary>
        /// Játékos lekérdezése és beállítása. Ha új játékost állítunk be, újrakezdődik a játék.
        /// </summary>
        public JellystonePlayer Player { get { return _player; } set { _player = value; RestartGame(); } }

        /// <summary>
        /// Nehézségi szint lekérdezése, beállítása.
        /// </summary>
        public GameDifficulty Difficulty
        {
            get
            {
                switch ((int)_keeperTimer.Interval)
                {
                    case 1500:
                        return GameDifficulty.Easy;
                    case 1000:
                        return GameDifficulty.Moderate;
                    default:
                        return GameDifficulty.Hard;
                }
            }

            set
            {
                switch (value)
                {
                    case GameDifficulty.Easy:
                        _keeperTimer.Interval = 1500;
                        break;
                    case GameDifficulty.Moderate:
                        _keeperTimer.Interval = 1000;
                        break;
                    default:
                        _keeperTimer.Interval = 500;
                        break;
                }
                if (_initialTables != null && Tables != null)
                    RestartGame(); //Ne lehessen menet közben állítani a nehézséget.
            }
        }

        /// <summary>
        /// Akutális pályához tartozó tábla lekérdezése.
        /// </summary>
        public JellystoneTable CurrentTable { get { return Tables[CurrentLevel]; } set { _tables[CurrentLevel] = value; } }

        /// <summary>
        /// Játék szüneteltetésének lekérdezése.
        /// </summary>
        public bool Paused { get { return !_gameTimer.Enabled; } }

        /// <summary>
        /// Játékkal eltöltött idő lekérdezése.
        /// </summary>
        public int GameTime { get { return _gameTime; } }

        /// <summary>
        /// Játék végének ellenőrzése.
        /// </summary>
        public bool IsOver { get { return _isLevelOver; } }

        #endregion

        #region Events

        /// <summary>
        /// Játék inicializáltságának eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> Initialized;

        /// <summary>
        /// Játék végének eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> LevelOver;

        /// <summary>
        /// Játékidő léptetésének eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> Advanced;

        /// <summary>
        /// Maci Laci lépésének eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> Stepped;

        /// <summary>
        /// Figurák mozgatásának eseménye.
        /// </summary>
        public event EventHandler<JellystoneVectorEventArgs> FigureMoved;

        /// <summary>
        /// Kosár találás eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> GotBasket;

        /// <summary>
        /// Játék végének eseménye.
        /// </summary>
        public event EventHandler<JellystoneResultEventArgs> GameOver;

        #endregion

        #region Constructors

        public JellystoneModel()
        {
            Init();
        }

        /// <summary>
        /// A Jellystone játék példányosítása.
        /// </summary>
        public JellystoneModel(string playerName, JellystoneModel.GameDifficulty diff = GameDifficulty.Moderate)
        {
            Init();
            _player = new JellystonePlayer(playerName);
            Difficulty = diff; //Csak az Init() után szabad kiadni ezt az utasítást, különben üres objektumot próbálunk módosítani.
        }

        #endregion

        #region Dispose methods

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this._disposed)
            {
                if (disposing)
                {
					if (null != _gameTimer) _gameTimer.Dispose();					
					if (null != _keeperTimer) _keeperTimer.Dispose();
                }
				
				_gameTimer = null;
				_keeperTimer = null;
                _disposed = true;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Mezők inicializálása szint kezdéséhez.
        /// </summary>
        public void InitLevel()
        {
            _steps = 0;
            _gameTime = 0;
            _basketsFounded = 0;
            _isLevelOver = false;
            _isLevelWon = false;
            _basketsAll = Tables[_currentLevel].CountBaskets;
            OnInitialized();
        }

        /// <summary>
        /// Játék elindítása paraméterként kapott pályával.
        /// </summary>
        public void NewGame(List<JellystoneTable> tables)
        {
            _currentLevel = 0;
            _initialTables = new List<JellystoneTable>(tables);
            RestoreTables();
            InitLevel();
        }

        /// <summary>
        /// Első szintre ugrás, szint kezdése.
        /// </summary>
        public void RestartGame()
        {
            _currentLevel = 0;
            _player.GameTime = 0;
            _player.Steps = 0;
            RestoreTables();
            InitLevel();
            Continue();
        }

        /// <summary>
        /// Szint újrakezdése.
        /// </summary>
        public void RestartLevel()
        {
            CurrentTable = new JellystoneTable(_initialTables[CurrentLevel]);
            InitLevel();
            Continue();
        }

        /// <summary>
        /// Lépés következő szintre.
        /// </summary>
        public void NextLevel()
        {
            if (_currentLevel + 1 < Tables.Count)
            {
                ++_currentLevel;
                InitLevel();
            }
            else if (GameOver != null)
                GameOver(this, new JellystoneResultEventArgs(_player.GameTime, _player.Steps, 0, 0, true));
        }

        /// <summary>
        /// Játék szüneteltetése
        /// </summary>
        public void Pause()
        {
            if (Paused)
                return;

            _gameTimer.Stop();
            _keeperTimer.Stop();
        }

        /// <summary>
        /// Játék folytatása
        /// </summary>
        public void Continue()
        {
            if (!Paused || IsOver)
                return;

            _gameTimer.Start();
            _keeperTimer.Start();
        }

        /// <summary>
        /// Szüneteltetés/Folytatás
        /// </summary>
        public void Toggle()
        {
            if (!Paused)
                Pause();
            else
                Continue();
        }

        /// <summary>
        /// Játékos lép fel.
        /// </summary>
        public void StepUp()
        {
            StepTo(CurrentTable.YogyCurrentPos.Item1, CurrentTable.YogyCurrentPos.Item2 - 1);
        }

        /// <summary>
        /// Játékos lép jobbra.
        /// </summary>
        public void StepRight()
        {
            StepTo(CurrentTable.YogyCurrentPos.Item1 + 1, CurrentTable.YogyCurrentPos.Item2);
        }

        /// <summary>
        /// Játékos lép le.
        /// </summary>
        public void StepDown()
        {
            StepTo(CurrentTable.YogyCurrentPos.Item1, CurrentTable.YogyCurrentPos.Item2 + 1);
        }

        /// <summary>
        /// Játékos lép balra.
        /// </summary>
        public void StepLeft()
        {
            StepTo(CurrentTable.YogyCurrentPos.Item1 - 1, CurrentTable.YogyCurrentPos.Item2);
        }

        #endregion

        #region Private event handler

        /// <summary>
        /// Játék időzítőjének eseménykezelője.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _gameTimer_Elapsed(object sender, EventArgs e)
        {
            OnAdvance();
        }

        /// <summary>
        /// Vadőrök időzítőjének eseménykezelője.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _keeperTimer_Elapsed(object sender, EventArgs e)
        {
            List<Tuple<int, int>> movedKeepers = new List<Tuple<int, int>>();
            for (int i = 0; i < CurrentTable.SideLength; ++i)
                for (int j = 0; j < CurrentTable.SideLength; ++j)
                    if (!movedKeepers.Contains(Tuple.Create<int, int>(i, j))) //A már mozgatott vadőröket kihagyjuk
                        switch (CurrentTable[i, j])
                        {
                            case JellystoneTable.JellystoneField.RangerHL:
                                if (!CurrentTable.SetValue(i - 1, j, JellystoneTable.JellystoneField.RangerHL))
                                {
                                    if (CurrentTable.SetValue(i + 1, j, JellystoneTable.JellystoneField.RangerHR))
                                    {
                                        CurrentTable[i, j] =  JellystoneTable.JellystoneField.Blank;
                                        OnFigureMoved(i, j, i + 1, j);
                                        movedKeepers.Add(Tuple.Create<int, int>(i + 1, j));
                                        if (IsInTheNeighborhood(i + 1, j, new JellystoneTable.JellystoneField[] { JellystoneTable.JellystoneField.Yogi }))
                                            OnLevelOver();
                                    }
                                }
                                else
                                {
                                    CurrentTable[i, j] = JellystoneTable.JellystoneField.Blank;
                                    OnFigureMoved(i, j, i - 1, j);
                                    movedKeepers.Add(Tuple.Create<int, int>(i - 1, j));
                                    if (IsInTheNeighborhood(i - 1, j, new JellystoneTable.JellystoneField[] { JellystoneTable.JellystoneField.Yogi }))
                                        OnLevelOver();
                                }
                                break;

                            case JellystoneTable.JellystoneField.RangerHR:
                                if (!CurrentTable.SetValue(i + 1, j, JellystoneTable.JellystoneField.RangerHR))
                                {
                                    if (CurrentTable.SetValue(i - 1, j, JellystoneTable.JellystoneField.RangerHL))
                                    {
                                        CurrentTable[i, j] = JellystoneTable.JellystoneField.Blank;
                                        OnFigureMoved(i, j, i - 1, j);
                                        movedKeepers.Add(Tuple.Create<int, int>(i - 1, j));
                                        if (IsInTheNeighborhood(i - 1, j, new JellystoneTable.JellystoneField[] { JellystoneTable.JellystoneField.Yogi }))
                                            OnLevelOver();
                                    }
                                }
                                else
                                {
                                    CurrentTable[i, j] = JellystoneTable.JellystoneField.Blank;
                                    OnFigureMoved(i, j, i + 1, j);
                                    movedKeepers.Add(Tuple.Create<int, int>(i + 1, j));
                                    if (IsInTheNeighborhood(i + 1, j, new JellystoneTable.JellystoneField[] { JellystoneTable.JellystoneField.Yogi }))
                                        OnLevelOver();
                                }
                                break;

                            case JellystoneTable.JellystoneField.RangerVD:
                                if (!CurrentTable.SetValue(i, j + 1, JellystoneTable.JellystoneField.RangerVD))
                                {
                                    if (CurrentTable.SetValue(i, j - 1, JellystoneTable.JellystoneField.RangerVU))
                                    {
                                        CurrentTable[i, j] = JellystoneTable.JellystoneField.Blank;
                                        OnFigureMoved(i, j, i, j - 1);
                                        movedKeepers.Add(Tuple.Create<int, int>(i, j - 1));
                                        if (IsInTheNeighborhood(i, j - 1, new JellystoneTable.JellystoneField[] { JellystoneTable.JellystoneField.Yogi }))
                                            OnLevelOver();
                                    }
                                }
                                else
                                {
                                    CurrentTable[i, j] = JellystoneTable.JellystoneField.Blank;
                                    OnFigureMoved(i, j, i, j + 1);
                                    movedKeepers.Add(Tuple.Create<int, int>(i, j + 1));
                                    if (IsInTheNeighborhood(i, j + 1, new JellystoneTable.JellystoneField[] { JellystoneTable.JellystoneField.Yogi }))
                                        OnLevelOver();
                                }
                                break;

                            case JellystoneTable.JellystoneField.RangerVU:
                                if (!CurrentTable.SetValue(i, j - 1, JellystoneTable.JellystoneField.RangerVU))
                                {
                                    if (CurrentTable.SetValue(i, j + 1, JellystoneTable.JellystoneField.RangerVD))
                                    {
                                        CurrentTable[i, j] = JellystoneTable.JellystoneField.Blank;
                                        OnFigureMoved(i, j, i, j + 1);
                                        movedKeepers.Add(Tuple.Create<int, int>(i, j + 1));
                                        if (IsInTheNeighborhood(i, j + 1, new JellystoneTable.JellystoneField[] { JellystoneTable.JellystoneField.Yogi }))
                                            OnLevelOver();
                                    }
                                }
                                else
                                {
                                    CurrentTable[i, j] = JellystoneTable.JellystoneField.Blank;
                                    OnFigureMoved(i, j, i, j - 1);
                                    movedKeepers.Add(Tuple.Create<int, int>(i, j - 1));
                                    if (IsInTheNeighborhood(i, j - 1, new JellystoneTable.JellystoneField[] { JellystoneTable.JellystoneField.Yogi }))
                                        OnLevelOver();
                                }
                                break;
                        }
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Mezők inicializálása.
        /// </summary>
        private void Init()
        {
            _gameTimer = new PCLTimer();
            _gameTimer.Interval = 1000;
            _keeperTimer = new PCLTimer();

            _gameTimer.Elapsed += _gameTimer_Elapsed;
            _keeperTimer.Elapsed += _keeperTimer_Elapsed;
        }

        /// <summary>
        /// Megpróbál Maci Lacival a paraméterként megadott koordinátákra lépni.
        /// </summary>
        /// <returns>Ha sikerült a megadott koordinátákra lépni igaz, különben hamis.</returns>
        private void StepTo(int i, int j)
        {
            if (Paused)
                return;
            Tuple<int, int> currPos = Tuple.Create<int, int>(CurrentTable.YogyCurrentPos.Item1, CurrentTable.YogyCurrentPos.Item2);
            JellystoneTable.JellystoneField toField = CurrentTable[i, j];
            if (CurrentTable.SetValue(i, j, JellystoneTable.JellystoneField.Yogi))
            {
                CurrentTable[currPos.Item1, currPos.Item2] = JellystoneTable.JellystoneField.Blank;
                
                // Ha Maci Laci öngyilkos akar lenni
                if (IsInTheNeighborhood(i, j, new JellystoneTable.JellystoneField[]{ JellystoneTable.JellystoneField.RangerHL,
                                                                           JellystoneTable.JellystoneField.RangerHR,
                                                                           JellystoneTable.JellystoneField.RangerVD,
                                                                           JellystoneTable.JellystoneField.RangerVU
                                                                          }
                                       )
                    )
                {
                    OnFigureMoved(currPos.Item1, currPos.Item2, i, j);
                    OnStep();
                    OnLevelOver();
                    return;
                }
                OnFigureMoved(currPos.Item1, currPos.Item2, i, j);
                OnStep();
                if (JellystoneTable.JellystoneField.Basket == toField)
                    OnGotBasket();
            }
        }

        /// <summary>
        /// Adott hely szomszédságában van-e f értékű mező.
        /// </summary>
        /// <param name="i">Oszlopindex</param>
        /// <param name="j">Sorindex</param>
        /// <returns>Ha van igaz, különben hamis.</returns>
        private bool IsInTheNeighborhood(int i, int j, IEnumerable<JellystoneTable.JellystoneField> f)
        {
            if (f.Contains(CurrentTable[i - 1, j]) ||
                f.Contains(CurrentTable[i - 1, j - 1]) ||
                f.Contains(CurrentTable[i - 1, j + 1]) ||
                f.Contains(CurrentTable[i, j - 1]) ||
                f.Contains(CurrentTable[i, j + 1]) ||
                f.Contains(CurrentTable[i + 1, j - 1]) ||
                f.Contains(CurrentTable[i + 1, j]) ||
                f.Contains(CurrentTable[i + 1, j + 1])
                )
                return true;
            return false;
        }

        /// <summary>
        /// Játéktáblák kezdeti állapotának visszaállítása.
        /// </summary>
        /// <exception cref="ArgumentNullException">Ha nem lehetett visszaállítani a táblákat.</exception>
        private void RestoreTables()
        {
            if (null != Tables)
                _tables.Clear();
            try
            {
                _tables = new List<JellystoneTable>(_initialTables.Count);
                foreach (var item in _initialTables)
                     _tables.Add(new JellystoneTable(item));
            }
            catch (Exception ex)
            {
                throw new ArgumentNullException("Táblák visszaállítása nem sikerült!" + Environment.NewLine + ex.Message);
            }
        }

        #endregion

        #region Private event methods

        /// <summary>
        /// Inicializálás eseménykiváltása.
        /// </summary>
        private void OnInitialized()
        {
            if (null != Initialized)
                Initialized(this, new JellystoneResultEventArgs(_gameTime, _steps, _basketsFounded, _basketsAll, _isLevelWon));
        }

        /// <summary>
        /// Játék előrehaladás eseményének kiváltása.
        /// </summary>
        private void OnAdvance()
        {
            if (null != Advanced)
                Advanced(this, new JellystoneResultEventArgs(++_gameTime, _steps, _basketsFounded, _basketsAll, _isLevelWon));
        }

        /// <summary>
        /// Lépés eseményének kiváltása.
        /// </summary>
        private void OnStep()
        {
            if (null != Stepped)
                Stepped(this, new JellystoneResultEventArgs(_gameTime, ++_steps, _basketsFounded, _basketsAll, _isLevelWon));
        }

        /// <summary>
        /// Figura elmozdulás eseményének kiváltása.
        /// </summary>
        /// <param name="x">Forrás x koordináta</param>
        /// <param name="y">Forrás y koordináta</param>
        /// <param name="xt">Cél x koordináta</param>
        /// <param name="yt">Cél y koordináta</param>
        private void OnFigureMoved(int xf, int yf, int xt, int yt)
        {
            if (null != FigureMoved)
                FigureMoved(this, new JellystoneVectorEventArgs(xf, yf, xt, yt));
        }

        /// <summary>
        /// Kosár begyűjtésének kiváltása. Ha meg van az összes, győzelem.
        /// </summary>
        private void OnGotBasket()
        {
            if (null != GotBasket)
                GotBasket(this, new JellystoneResultEventArgs(_gameTime, _steps, ++_basketsFounded, _basketsAll, _isLevelWon));
            if (_basketsAll == _basketsFounded) //Nyertünk-e
            {
                _isLevelWon = true;
                OnLevelOver();
            }
        }

        /// <summary>
        /// Szint végének kiváltása.
        /// </summary>
        private void OnLevelOver()
        {
            if (null != LevelOver)
            {
                _isLevelOver = true;
                _player.GameTime += _gameTime;
                _player.Steps += _steps;
                Pause();
                LevelOver(this, new JellystoneResultEventArgs(_gameTime, _steps, _basketsFounded, _basketsAll, _isLevelWon));
            }
        }

        #endregion    

    }
}
